// Final Project for CS 184, Developed by Dustin Shean and Ahmed Afifi (Version 11.30.11, 1:34 AM)
#include <vector>
#include <iostream>

#include <sstream>
#include <cmath>
#include <cstdio>

#include "algebra3.h"
#include "parser.h"


#include "engine.h"

#ifdef _WIN32
#	include <windows.h>
#else
#	include <sys/time.h>
#endif

#ifdef OSX
#include <GLUT/glut.h>
#include <OpenGL/glu.h>
#else
#include <GL/glut.h>
#include <GL/glu.h>
#endif

#include <time.h>
#include <math.h>

#ifdef _WIN32
static DWORD lastTime;
#else
static struct timeval lastTime;
#endif




using namespace std;

// GLOBAL VARS
Viewport			viewport;
Camera              camera;
Model				castle;

// variables for FPS
int                 frame=0;
float               currentTime;
float               timebase,jumpTime,strafeTime;
char                s[50];

Character	enemy;
vector<vec3> enemy_pos;
Spell		spells;

//default font
void                *font = GLUT_STROKE_ROMAN;

// key states
float               deltaAngle = 0.0f;
float               deltaMove  = 0.0f;
int                 xOrigin = -1;
int                 strafe = 0;
bool                jumping = false;

// settings
bool                light = true;
bool                smooth = true;
bool                wired = false;
bool                hidden = false;

GLfloat xrot;                                   // X Rotation
GLfloat yrot;                                   // Y Rotation
GLfloat xtrans;                                 // X Translation
GLfloat ytrans;                                 // Y Translation
GLfloat ztrans = -10.0f;

//****************************************************
// reshape viewport if the window is resized
//****************************************************
void myReshape(int w, int h) {
    viewport.w = w;
    viewport.h = h;

    glViewport(0,0,viewport.w,viewport.h);// sets the rectangle that will be the window
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();				// loading the identity matrix for the screen

    gluPerspective(45.0f,(GLfloat)w/(GLfloat)h,0.1f,100.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    //------------------------------------------------------------
    }


//****************************************************
// sets the window up
//****************************************************
void initLight(){
	GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };
	GLfloat LightDiffuse[]= { 0.6f, 0.6f, 0.6f, 1.0f };
	GLfloat LightPosition[]= { 2.0f, 2.0f, 0.0f, 1.0f };	
	
    glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
    glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);
    glEnable(GL_LIGHT1);
    glEnable(GL_LIGHTING);
}

void initScene(){
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Clear to black, fully transparent

    glShadeModel(GL_SMOOTH);  // Set smooth shading
    glEnable(GL_DEPTH_TEST);                        // Enables Depth Testing
    glEnable(GL_CULL_FACE);
    glClearDepth(1.0f);                         // Depth Buffer Setup
    glDepthFunc(GL_LEQUAL);                         // The Type Of Depth Test To Do
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);          // Really Nice Perspective Calculations

	initLight();
	
    myReshape(viewport.w,viewport.h);
}


//***************************************************
// Renders a string
//***************************************************
void renderBitmapString(
		float x,
		float y,
		float z,
		void *font,
		char *string) {

	char *c;
	glRasterPos3f(x, y,z);
	for (c=string; *c != '\0'; c++) {
		glutBitmapCharacter(font, *c);
	}
}

void renderStrokeFontString(float x, float y, float z, void *font, char *string){
    char *c;
    glPushMatrix();
    glTranslatef(x, y, z);
    glScalef(0.002f, 0.002f, 0.002f);
    for (c=string; *c != '\0'; c++) {
        glutStrokeCharacter(font, *c);
        }
    glPopMatrix();
   
    }

void restorePerspectiveProjection() {
    glMatrixMode(GL_PROJECTION);
    // restore previous projection matrix
    glPopMatrix();

    // get back to modelview mode
    glMatrixMode(GL_MODELVIEW);
    
    }

void setOrthographicProjection() {
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0,viewport.w,viewport.h,0);
    glMatrixMode(GL_MODELVIEW);
    }

void computePosition() {
    camera.x += deltaMove * camera.lx * 0.025f;
    camera.z += deltaMove * camera.lz * 0.025f;
    }

void computeJump() {
    float timeElapsed = (currentTime - jumpTime) / 1000;
    camera.y = (camera.floorHeight * -10.0 * pow(timeElapsed,2)) + (7 * timeElapsed) + camera.floorHeight;
    if(camera.y < camera.floorHeight + 0.5){
        camera.y = camera.floorHeight + 0.5;
        jumping = false;
        }
    }

void computeStrafe() {
    float timeElapsed = (currentTime - strafeTime) / 1000;
    camera.x += strafe * cos(camera.angle) * 0.025 * timeElapsed; 
    camera.z += strafe * sin(camera.angle) * 0.025 * timeElapsed;
    }

void loadFloor() {
	GLfloat diffuse[] = {0, 0, 0};
	glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
    glBegin(GL_QUADS);
		glVertex3f(-100.0f, 0.0f, -100.0f);
		glVertex3f(-100.0f, 0.0f,  100.0f);
		glVertex3f( 100.0f, 0.0f,  100.0f);
		glVertex3f( 100.0f, 0.0f, -100.0f);
    glEnd();

    }

float euclidianDistance(float x1,float y1,float z1,float x2,float y2,float z2){
    return sqrt(pow(x1-x2,2) + pow(y1-y2,2) + pow(z1-z2,2));
    }

//***************************************************
// function that does the actual drawing
//***************************************************
void myDisplay() {

    currentTime=glutGet(GLUT_ELAPSED_TIME);

    if (deltaMove) {
        computePosition();
        }

    if (jumping) {
        computeJump();
        }

    if (strafe)  {
        computeStrafe();
        }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);				// clear the color buffer (sets everything to black)
    glLoadIdentity();							                    // make sure transformation is "zero'd"
        
    // set the camera
    gluLookAt(  camera.x,           camera.y, camera.z,
                camera.x+camera.lx, camera.y, camera.z+camera.lz,
                0.0f,               1.0f,     0.0f);

    // Draw the ground
    loadFloor();

    // Draw objects
    glTranslatef(0.0f,0.5f,0.0f);
	for(int i = -3; i < 3; i++)
		for(int j=-3; j < 3; j++) {
            glColor3f(i*0.5f,0.0f,j * 0.5f);
			glPushMatrix();
			glTranslatef(i*10.0,0,j * 10.0);
			castle.draw();
            castle.drawBB();
			glPopMatrix();
		}
	
	enemy.draw();
	
	//Casting Spells
	for (int i = 0; i < maxSpells; i++){
		if (spells.magic[i].casted)
			spells.magic[i].casting();		
	}

    // Compute FPS
    frame++;

    currentTime=glutGet(GLUT_ELAPSED_TIME);
    if (currentTime - timebase > 1000) {
		sprintf(s,"Final Project - FPS:%4.2f",
			frame*1000.0/(currentTime-timebase));
        timebase = currentTime;
        frame = 0;
        }

    // Code to display a string (fps) with bitmap fonts
	setOrthographicProjection();

	glPushMatrix();
	glLoadIdentity();
	renderBitmapString(5,30,0,GLUT_BITMAP_HELVETICA_18,s);
	glPopMatrix();

	restorePerspectiveProjection();

	glutSwapBuffers();

    }




//****************************************************
// called by glut when there are no messages to handle
//****************************************************
void myFrameMove() {
    //nothing here for now
#ifdef _WIN32
    Sleep(10);						//give ~10ms back to OS (so as not to waste the CPU)
#endif
    glutPostRedisplay(); // forces glut to call the display function (myDisplay())
    }


//****************************************************
// Called when normal keys are pressed
//****************************************************
void processNormalKeys(unsigned char key, int x, int y) {

    switch (key) {
    case 32 :              // Space Key
        if(!jumping){
            jumping = true;
            jumpTime = glutGet(GLUT_ELAPSED_TIME);
            }
        break;
    case 'l' :
        light=!light;
        if(!light){
            glDisable(GL_LIGHTING);
            } else {
                glEnable(GL_LIGHTING);
            }
        break;
    case 'c' :
        smooth = !smooth;
        if(smooth){
            glShadeModel(GL_SMOOTH);
            } else {
                glShadeModel(GL_FLAT);
            }
        break;
    case 'r' :
        wired = !wired;
        if(wired){
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
            } else {
                glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            }
        break;
    case 'w' :
        deltaMove = 0.5f,deltaMove = 0.5f;
        break;
    case 's' :
        deltaMove = -0.5f,deltaMove = -0.5f;
        break;     
    case 'd' :
        strafe = 1;
        strafeTime = glutGet(GLUT_ELAPSED_TIME);
        break;
    case 'a' :
        strafe = -1;
        strafeTime = glutGet(GLUT_ELAPSED_TIME);
        break;
	case 'm':
		spells.cast();
		break;
        }
    }

//****************************************************
// Called when arrow keys are pressed
//****************************************************
void processSpecialKeys(int key, int x, int y) {

    int mod = glutGetModifiers();

    switch (key) {
    case GLUT_KEY_LEFT :
        break;
    case GLUT_KEY_RIGHT :
        break;
    case GLUT_KEY_UP :
        deltaMove = 0.5f;
        break;
    case GLUT_KEY_DOWN :
        deltaMove = -0.5f;
        break;
        }
    }

void releaseKey(unsigned char key, int x, int y) {

	switch (key) {
		case 'w' :
		case 's' :
            deltaMove = 0;
            break;
        case 'a' :
        case 'd' :
            strafe    = false;
            break;
	}
}

void mouseMove(int x, int y) {

	// this will only be true when the left button is down
	if (xOrigin >= 0) {

		// update deltaAngle
		deltaAngle = (x - xOrigin) * 0.001f;

		// update camera's direction
		camera.lx = sin(camera.angle + deltaAngle);
		camera.lz = -cos(camera.angle + deltaAngle);
	}
}

void mouseButton(int button, int state, int x, int y) {

	// only start motion if the left button is pressed
	if (button == GLUT_LEFT_BUTTON) {

		// when the button is released
		if (state == GLUT_UP) {
			camera.angle += deltaAngle;
			xOrigin = -1;
		}
		else  {// state = GLUT_DOWN
			xOrigin = x;
		}
	}
}



Camera getCamera(){
    return camera;
    }


//****************************************************
// the usual stuff, nothing exciting here
//****************************************************
int main(int argc, char *argv[]) {
    //This initializes glut
    glutInit(&argc, argv);

    //This tells glut to use a double-buffered window with red, green, and blue channels 
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_ALPHA | GLUT_DEPTH );

    //The size and position of the window
    glutInitWindowSize(viewport.w, viewport.h);
    glutInitWindowPosition(0, 0);
    glutCreateWindow("Final Project");

    initScene();							// quick function to set up scene

	castle = getInput("Castle.obj");
	vec3 v1 = vec3(0, .5, -10);
	enemy = Character();
	enemy.pos = v1;
	spells.set();
	
    glutDisplayFunc(myDisplay);				// function to run when its time to draw something
    glutReshapeFunc(myReshape);				// function to run when the window gets resized
    glutIdleFunc(myDisplay);				// function to run when not handling any other task

    glutKeyboardFunc(processNormalKeys);    // function to run when user inputs key
    glutSpecialFunc(processSpecialKeys);    // function to run when user inputs arrow key
    glutIgnoreKeyRepeat(1);
    glutKeyboardUpFunc(releaseKey);
	glutMouseFunc(mouseButton);
	glutMotionFunc(mouseMove);

    glutMainLoop();							// infinite loop that will keep drawing and resizing and whatever else

    return 0;
}